﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using Microsoft.Win32;

namespace SetupCustomActionOne
{
    // This is the way it was first designed.  However it turned out that the
    // uninstall special action is run without admin rights (!!!) and after the
    // install directory context it lost.  Accordingly there are two operating
    // modes now - see below.

    // This is the install operating mode
    // Acts like a tiny shell interpreting commands from the command line parameters
    // Supports:  exec <program with arguments>  -- not checked recently
    //            execvs <program with arguments to execute in Visual Studio context>
    //            copy <fromfile> <tofile>
    //            move <fromfile> <tofile>
    //            delete <filename>
    // Must be called as follows
    //            actionone <installdirectory> <command>s...
    //
    // where <installdirectory> is the target installation directory for the setup
    //   and <command> takes the form <moodifier> <action>, ie:
    //            [2010 | 2012 | 2013 | all | any] <action - as above>
    // if either 2010, 2012 or 2013 is present the action is conditional on that version
    // of Visual Studio being already installed on the target machine.  If "all" or "any"
    // then either or both will be actioned as appropriate
    // Each file name can be preceded by i: (installation directory) or v: (Visual
    // Studio directory).  Any command with a filename that includes v: must have
    // a modifier.  Copy, move and delete work recursively with directories, or 
    // just with simple files
    //
    // There is also an uninstall operating mode - Activated by calling the
    // program as follows:
    //            actionone POSTUNINSTALL <command>s...
    //
    // This only supports the execvs and delete commands and they cannot be 
    // without a modifier - ie: unmodified commands are ignored

    // Expected settings are for install - note the space after [INSTALLDIR] - that is needed - macro expansion removes one character
    // "[INSTALLDIR] " all copy i:Templates\Projects\JBBRXG11 v:ProjectTemplates\CSharp 2010 copy i:Templates\Items\* "v:Extensions\Microsoft\XNA Game Studio 4.0\ItemTemplates\CSharp" 2012 copy i:Templates\Items\* "v:Extensions\Microsoft\XNA Game Studio 4.0\ItemTemplates\CSharp"  2013 copy i:Templates\Items\* v:ItemTemplates\CSharp all execvs "devenv /setup"
    // Test example
    // "C:\\Program Files (x86)\\University of Waikato\\JBBRXG11\\" all copy i:Templates\Projects\JBBRXG11 v:ProjectTemplates\CSharp 2010 copy i:Templates\Items\* "v:Extensions\Microsoft\XNA Game Studio 4.0\ItemTemplates\CSharp" 2012 copy i:Templates\Items\* "v:Extensions\Microsoft\XNA Game Studio 4.0\ItemTemplates\CSharp"  2013 copy i:Templates\Items\* v:ItemTemplates\CSharp all execvs "devenv /setup"
    // and for uninstall
    // POSTUNINSTALL all delete v:ProjectTemplates\CSharp\JBBRXG11 2010 delete "v:Extensions\Microsoft\XNA Game Studio 4.0\ItemTemplates\CSharp\JBBRXG11*.zip" 2012 delete "v:Extensions\Microsoft\XNA Game Studio 4.0\ItemTemplates\CSharp\JBBRXG11*.zip" 2013 delete "v:ItemTemplates\CSharp\JBBRXG11*.zip" all execvs "devenv /setup"
    // which was in version one
    // POSTUNINSTALL 2010 delete v:ProjectTemplates\CSharp\JBBRXG11 2010 delete "v:Extensions\Microsoft\XNA Game Studio 4.0\ItemTemplates\CSharp\JBBRXG11*.zip" 2010 execvs "devenv /setup"

    public partial class Form1 : Form
    {
        string VS2010dir; // all including final backslash
        string VS2012dir;
        string VS2013dir;
        string installationdir;  
        int nextarg;
        string arg;
        bool debugmode = false;

        public Form1()
        {
            InitializeComponent();
        }

        void println(string msg) { LogText.Text += msg + "\r\n"; }

        private bool getVSinstalldir(string regkey, string valuename, out string result)
        {
            result = "";
            RegistryKey key = Registry.LocalMachine;
            if (key == null) return false;
            key = key.OpenSubKey(regkey);
            if (key == null) return false;
            object vslocation = key.GetValue("InstallDir");
            if (vslocation == null || !(vslocation is string)) return false;
            result = vslocation as string;
            return true;
        }

        string makefilename(string basename, string vsdir)
        {
            if (basename.Length >= 2 && basename.Substring(0, 2) == "i:")
            {
                return installationdir + basename.Substring(2);
            }
            else if (basename.Length >= 2 && basename.Substring(0, 2) == "v:")
            {
                return vsdir + basename.Substring(2);
            }
            else
            {
                return basename;
            }
        }

        void doexec(string arg1)
        {
            println("EXEC " + arg1);
            try
            {
                System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(arg1);
                psi.UseShellExecute = true;
                if (!debugmode) psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                System.Diagnostics.Process action = new System.Diagnostics.Process();
                action.StartInfo = psi;
                action.Start();
                action.WaitForExit();
            }
            catch (Exception exc)
            {
                println("Error in exec: " + exc.Message);
            }
        }

        void doexecvs(string arg1, string vsdir)
        {
            println("EXECVS " + arg1);
            string commandfilename = installationdir + "action.bat";
            StreamWriter sw = new StreamWriter(commandfilename);
            sw.WriteLine("call \"" + vsdir + "..\\..\\VC\\vcvarsall.bat\" x86");
            sw.WriteLine("echo Starting Visual Studio command");
            sw.WriteLine(arg1);
            if (debugmode)
            {
                sw.WriteLine("echo Done!!!");
                sw.WriteLine("pause");
            }
            sw.Close();
            println("Written command file: " + commandfilename);
            try
            {
                println("Running <<" + commandfilename + ">>");
                System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(commandfilename);
                psi.UseShellExecute = true;
                if (!debugmode) psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                System.Diagnostics.Process action = new System.Diagnostics.Process();
                action.StartInfo = psi;
                action.Start();
                action.WaitForExit();
            }
            catch (Exception exc)
            {
                println("Error in execvs: " + exc.Message);
            }
            File.Delete(commandfilename);
        }

        void myfilecopy(string f1, string f2)
        {
            println("... copy " + f1 + " to " + f2);
            try
            {
                File.Copy(f1, f2);
            }
            catch (Exception exc)
            {
                println("Copy error: " + exc.Message);
            }
        }

        void myfilemove(string f1, string f2)
        {
            println("... move " + f1 + " to " + f2);
            try
            {
                File.Move(f1, f2);
            }
            catch (Exception exc)
            {
                println("Move error: " + exc.Message);
            }
        }

        void mycreatedirectory(string dir)
        {
            println("... mkdir " + dir);
            try
            {
                Directory.CreateDirectory(dir);
            }
            catch (Exception exc)
            {
                println("Create dir error: " + exc.Message);
            }
        }

        void mymovedirectory(string dir1, string dir2)
        {
            println("... move dir " + dir1 + " as " + dir2);
            try
            {
                Directory.Move(dir1, dir2);
            }
            catch (Exception exc)
            {
                println("Move dir error: " + exc.Message);
            }
        }

        void copydirectorycontents(string dir1, string dir2)
        {
            string[] files, dirs;
            if (Path.GetFileName(dir1).IndexOf('*') >= 0)
            {
                files = Directory.GetFiles(Path.GetDirectoryName(dir1), Path.GetFileName(dir1), SearchOption.TopDirectoryOnly);
                dirs = Directory.GetDirectories(Path.GetDirectoryName(dir1), Path.GetFileName(dir1), SearchOption.TopDirectoryOnly);
            }
            else
            {
                files = Directory.GetFiles(dir1);
                dirs = Directory.GetDirectories(dir1);
            }
            foreach (string file in files)
            {
                myfilecopy(file, Path.Combine(dir2, Path.GetFileName(file)));
            }
            foreach (string dir in dirs)
            {
                string target = Path.Combine(dir2, Path.GetFileName(dir));
                if (!Directory.Exists(target))
                    mycreatedirectory(target);
                copydirectorycontents(dir, target);
            }
        }

        void docopy(string arg1, string arg2)
        {
            println("COPY " + arg1);
            println("     " + arg2);
            if (Directory.Exists(arg1))
            {
                if (Directory.Exists(arg2))
                {
                    string target = Path.Combine(arg2, Path.GetFileName(arg1));
                    if (!Directory.Exists(target))
                        mycreatedirectory(target);
                    copydirectorycontents(arg1, target);
                }
                else
                {
                    mycreatedirectory(arg2);
                    copydirectorycontents(arg1, arg2);
                }
            }
            else if (Path.GetFileName(arg1).IndexOf('*') >= 0)
            {
                println("Wild card");
                if (!Directory.Exists(arg2)) mycreatedirectory(arg2);
                copydirectorycontents(arg1, arg2);
            }
            else if (Directory.Exists(arg2))
                myfilecopy(arg1, Path.Combine(arg2, Path.GetFileName(arg1)));
            else
                myfilecopy(arg1, arg2);
        }

        void domove(string arg1, string arg2)
        {
            println("MOVE " + arg1);
            println("     " + arg2);
            if (Directory.Exists(arg2))
                arg2 = Path.Combine(arg2, Path.GetFileName(arg1));
            if (Directory.Exists(arg1))
                mymovedirectory(arg1, arg2);
            else
                myfilemove(arg1, arg2);
        }

        void dodelete(string arg1)
        {
            println("DELETE " + arg1);
            try
            {
                if (Directory.Exists(arg1))
                {
                    println("... del dir: " + arg1);
                    Directory.Delete(arg1, true);
                }
                else
                {
                    println("... del: " + arg1);
                    File.Delete(arg1);
                }
            }
            catch (Exception exc)
            {
                println("Delete error: " + exc.Message);
            }
        }

        void docommand(string modifier, string command, string arg1, string arg2)
        {
            string vsdir = VS2010dir;
            if (modifier == "2012") vsdir = VS2012dir;
            if (modifier == "2013") vsdir = VS2013dir;
            if (vsdir == "") return;
            arg1 = makefilename(arg1, vsdir);
            arg2 = makefilename(arg2, vsdir);

            if (command == "exec") doexec(arg1);
            if (command == "execvs") doexecvs(arg1, vsdir);
            else if (command == "copy") docopy(arg1, arg2);
            else if (command == "move") domove(arg1, arg2);
            else if (command == "delete") dodelete(arg1);
        }

        void docommand(string modifier, string command, int narg)
        {
            string arg1 = "", arg2 = "";
            if (nextarg >= Program.myargs.Length) { println("Missing first argument (" + command + ")"); return; } else arg = Program.myargs[nextarg++];
            arg1 = arg;
            println("first argument: " + arg1);
            if (narg > 1)
            {
                if (nextarg >= Program.myargs.Length) { println("Missing second argument (" + command + ")"); return; } else arg = Program.myargs[nextarg++];
                arg2 = arg;
                println("second argument: " + arg2);
            }
            if (modifier == "all")
            {
                docommand("2010", command, arg1, arg2);
                docommand("2012", command, arg1, arg2);
                docommand("2013", command, arg1, arg2);
            }
            else if (modifier == "any")
            {
                if (VS2013dir != "")
                    docommand("2013", command, arg1, arg2);
                else if (VS2012dir != "")
                    docommand("2012", command, arg1, arg2);
                else
                    docommand("2010", command, arg1, arg2);
            }
            else if (modifier == "2010")
            {
                docommand("2010", command, arg1, arg2);
            }
            else if (modifier == "2012")
            {
                docommand("2012", command, arg1, arg2);
            }
            else if (modifier == "2013")
            {
                docommand("2013", command, arg1, arg2);
            }
            else if (modifier == "")
            {
                docommand("", command, arg1, arg2);
            }
        }

        bool FindVSDirectories()
        {
            if (!getVSinstalldir(@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\10.0", "InstallDir", out VS2010dir))
                getVSinstalldir(@"SOFTWARE\Microsoft\VisualStudio\10.0", "InstallDir", out VS2010dir);
            if (!getVSinstalldir(@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\11.0", "InstallDir", out VS2012dir))
                getVSinstalldir(@"SOFTWARE\Microsoft\VisualStudio\11.0", "InstallDir", out VS2012dir);
            if (!getVSinstalldir(@"SOFTWARE\Wow6432Node\Microsoft\VisualStudio\12.0", "InstallDir", out VS2013dir))
                getVSinstalldir(@"SOFTWARE\Microsoft\VisualStudio\12.0", "InstallDir", out VS2013dir);
            if (VS2010dir == "" && VS2012dir == "" && VS2013dir == "")
            {
                println("Neither version 2010, 2012 nor 2013 of Visual Studio is installed");
                return false;
            }
            if (VS2010dir != "") println("VS2010 at: " + VS2010dir);
            if (VS2012dir != "") println("VS2012 at: " + VS2012dir);
            if (VS2013dir != "") println("VS2013 at: " + VS2013dir);
            return true;
        }

        void DoNormalDirectProcessing()
        {
            println("Normal processing");
            println("Installation dir: " + installationdir);
            if (!FindVSDirectories()) return;

            for (; ; )
            {
                if (nextarg >= Program.myargs.Length) { println("Finished"); return; } else arg = Program.myargs[nextarg++];
                println("Possible command >>" + arg);
                string modifier = "";
                if (arg == "2010" || arg == "2012" || arg == "2013" || arg == "all" || arg == "any")
                {
                    modifier = arg;
                    if (nextarg >= Program.myargs.Length) { println("Finished"); return; } else arg = Program.myargs[nextarg++];
                }
                println("Modifier is " + modifier + " and command is " + arg);
                if (arg == "exec") docommand(modifier, arg, 1);
                else if (arg == "execvs") docommand(modifier, arg, 1);
                else if (arg == "copy") docommand(modifier, arg, 2);
                else if (arg == "move") docommand(modifier, arg, 2);
                else if (arg == "delete") docommand(modifier, arg, 1);
                else
                {
                    println("Bad command: " + arg);
                    break;
                }
            }
        }

        void WriteDeletesForDirectory(StreamWriter sw, string dir)
        {
            string[] files = Directory.GetFiles(dir);
            foreach (string file in files)
            {
                string deletecommand = "del \"" + file + "\"";
                println("Writing: " + deletecommand);
                sw.WriteLine(deletecommand);
            }
            string[] dirs = Directory.GetDirectories(dir);
            foreach (string onedir in dirs)
            {
                WriteDeletesForDirectory(sw, onedir);
            }
            string removecommand = "rmdir \"" + dir + "\"";
            println("Writing: " + removecommand);
            sw.WriteLine(removecommand);
        }

        void DoPostUninstall(string vsversion, string vsdir)
        {
            // This is for use in the Uninstall / Post System Change action handler.  As designed this
            // program doesn't work in that context for two reasons.  One is that the installation 
            // directory probably doesn't exist at this stage (managed by using the system temp dir,
            // because InstallShield's temp dir points somewhere in the Windows directory).
            // The second is more problematic - InstallShield does not run this action with admin
            // rights.  Accordingly this procedure rewrites part of the remainder of the program
            // to allow delete and exec to be done in a single batch file, which is run with elevated
            // rights.  An unfortunate side effect is that the user will be prompted for rights 
            // elevation twice - but at least the uninstall will be complete.
            println("Post uninstall processing for " + vsversion);

            try
            {
                installationdir = Path.GetTempPath();
                if (installationdir[installationdir.Length - 1] != '\\') installationdir += "\\";
                println("Installation directory: " + installationdir);

                string commandfilename = installationdir + "action.bat";
                StreamWriter sw = new StreamWriter(commandfilename);
                sw.WriteLine("call \"" + vsdir + "..\\..\\VC\\vcvarsall.bat\" x86");
                sw.WriteLine("echo Starting Visual Studio command");
                bool havecontent = false;

                for (; ; )
                {
                    if (nextarg >= Program.myargs.Length) { println("Scanning commands done"); break; } else arg = Program.myargs[nextarg++];
                    println("Possible command >>" + arg);
                    string modifier = "";
                    if (arg == "2010" || arg == "2012" || arg == "2013" || arg == "all" || arg == "any")
                    {
                        modifier = arg;
                        if (nextarg >= Program.myargs.Length) { println("Command incomplete"); return; } else arg = Program.myargs[nextarg++];
                    }
                    println("Modifier is <<" + modifier + ">>" + " and command is <<" + arg + ">>");

                    if (arg == "execvs")
                    {
                        if (nextarg >= Program.myargs.Length) { println("Command incomplete"); return; } else arg = Program.myargs[nextarg++];
                        if (modifier == vsversion || modifier == "all" || modifier == "any")  // Commands may be version specific - all or any always run
                        {
                            string execvscommand = makefilename(arg, vsdir);
                            println("Writing: " + execvscommand);
                            sw.WriteLine(execvscommand);
                            havecontent = true;
                        }
                    }
                    else if (arg == "delete")
                    {
                        if (nextarg >= Program.myargs.Length) { println("Command incomplete"); return; } else arg = Program.myargs[nextarg++];
                        if (modifier == vsversion || modifier == "all" || modifier == "any")  // Commands may be version specific - all or any always run
                        {
                            string target = makefilename(arg, vsdir);
                            if (Directory.Exists(target))
                            {
                                WriteDeletesForDirectory(sw, target);
                            }
                            else
                            {
                                string delcommand = "del \"" + makefilename(arg, vsdir) + "\"";
                                println("Writing: " + delcommand);
                                sw.WriteLine(delcommand);
                            }
                            havecontent = true;
                        }
                    }
                    else
                    {
                        println("Bad command: " + arg);
                        break;
                    }
                }

                if (debugmode)
                {
                    sw.WriteLine("echo Done!!!");
                    sw.WriteLine("pause");
                }
                sw.Close();
                println("Written command file: " + commandfilename);

                if (!havecontent)
                {
                    println("No content - not running the command file");
                }
                else
                {
                    try
                    {
                        println("Running <<" + commandfilename + ">>");
                        System.Diagnostics.ProcessStartInfo psi = new System.Diagnostics.ProcessStartInfo(commandfilename);
                        psi.UseShellExecute = true;
                        psi.Verb = "runas";
                        if (!debugmode) psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
                        System.Diagnostics.Process action = new System.Diagnostics.Process();
                        action.StartInfo = psi;
                        action.Start();
                        action.WaitForExit();
                    }
                    catch (Exception exc)
                    {
                        println("Error in execvs: " + exc.Message);
                    }
                }
                File.Delete(commandfilename);
            }
            catch (Exception exc)
            {
                println("Error in DoPostUninstall: " + exc.Message);
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            LogText.Text = "";

            for (int i = 0; i < Program.myargs.Length; i++)
                println(i.ToString() + " ==> " + Program.myargs[i]);

            nextarg = 0;
            if (nextarg >= Program.myargs.Length) { println("Finished"); return; } else arg = Program.myargs[nextarg++];
            installationdir = arg.Trim();
            println("Installation dir: " + installationdir);
            if (installationdir == "POSTUNINSTALL")
            {
                if (FindVSDirectories())
                {
                    int savenextarg = nextarg;
                    if (VS2010dir != "") DoPostUninstall("2010", VS2010dir);
                    nextarg = savenextarg;
                    if (VS2012dir != "") DoPostUninstall("2012", VS2012dir);
                    nextarg = savenextarg;
                    if (VS2013dir != "") DoPostUninstall("2013", VS2013dir);
                }
            }
            else
            {
                DoNormalDirectProcessing();
            }

            println("Finished");
            if (!debugmode) Close();
        }
    }
}
